﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Smled.States;

namespace Smled
{
    public partial class MapView : UserControl
    {
        protected TileMap map;
        private int offX, offY;
        private int selX, selY;
        private bool selIn;
        private const int TILESIZE = 32;
        private TileChooser tc;
        private int orgX, orgY;
        private int delX, delY;
        private int soffX, soffY;
        private Color mBackColor;
        private Brush mBackBrush;
        private List<MapEditEntry> mActionList;
        private int mActionID = -1;

        public event EventHandler<SelectionChangeEventArgs> SelectionChange;
        public event EventHandler MapChange;

        private bool isChanged;

        private MouseButtons primBtn = MouseButtons.Left;
        private MouseButtons altBtn = System.Windows.Forms.MouseButtons.Right;
        private MouseButtons scrollBtn = System.Windows.Forms.MouseButtons.Middle;

        private BaseState state = null;
        private bool scrollingEnabled = true;

        public int SelectedX { get { return selX; } }
        public int SelectedY { get { return selY; } }

        public MouseButtons PrimaryButton { get { return primBtn; } }
        public MouseButtons SecondaryButton { get { return altBtn; } }
        public MouseButtons ScrollButton { get { return scrollBtn; } }

        public Point selStart;
        public Point selEnd;
        public bool rectSelEnabled = false;

        public Point SelectionUL { get { return selStart; } }
        public Point SelectionDR { get { return selEnd; } }
        public bool RectSelection { get { return rectSelEnabled; } set { rectSelEnabled = value; Invalidate(); } }

        public BaseState State
        {
            get
            {
                return state;
            }
            set
            {
                if (state != value)
                {
                    if(state != null) state.Deactivate();
                    state = value;
                    state.Activate();
                    MainForm.Instance.activateState(state);
                }
                else
                {
                    if (state != null) { state.Reset(); MainForm.Instance.activateState(state); }
                }
            }
        }

        public bool ScrollingEnabled { get { return scrollingEnabled; } set { scrollingEnabled = value; } }

        public enum MapEditAction
        {
            None, PaintTile, ChangeBackColor
        }

        internal class MapEditEntry
        {
            private MapEditAction edType;
            private int posX, posY;
            private Tile t1, t2;
            private Color c1, c2;
            private bool mActive;
            private MapView mmw;

            public MapEditEntry(MapView mw, int posX, int posY, Tile T2)
            {
                edType = MapEditAction.PaintTile;
                this.posX = posX;
                this.posY = posY;
                t1 = mw.map.Map[posX, posY];
                t2 = T2;
                mmw = mw;
                mActive = true;
            }

            public MapEditEntry(MapView mw, Color Col)
            {
                edType = MapEditAction.ChangeBackColor;
                this.c1 = mw.BackColor;
                this.c2 = Col;
                mmw = mw;
                mActive = true;
            }

            public override string ToString()
            {
                string a = (mActive) ? "A) " : "X) ";
                switch (edType)
                {
                    case MapEditAction.ChangeBackColor:
                        return a + "ChangeBackColor : " + c1.ToString() + " -> " + c2.ToString();
                    case MapEditAction.PaintTile:
                        string st1 = (t1 == null) ? "null" : t1.Name;
                        string st2 = (t2 == null) ? "null" : t2.Name;
                        return a + "PaintTile : (" + posX.ToString() + ", " + posY.ToString() + ") " + st1 + " -> " + st2;
                    default:
                        return a + "Generic";
                }
            }

            public bool Redundant
            {
                get
                {
                    switch (edType)
                    {
                        case MapEditAction.ChangeBackColor:
                            return c1 == c2;

                        case MapEditAction.PaintTile:
                            return t1 == t2;

                        case MapEditAction.None:
                            return true;
                    }
                    return false;
                }
            }

            public bool EqualsOther(MapEditEntry me)
            {
                if (me.edType != this.edType) return false;

                switch (edType)
                {
                    case MapEditAction.ChangeBackColor:
                        return (me.c1 == this.c1) && (me.c2 == this.c2);

                    case MapEditAction.PaintTile:
                        return (me.posX == this.posX) && (me.posY == this.posY) &&
                            (me.t1 == this.t1) && (me.t2 == this.t2);
                    case MapEditAction.None:
                        return true;
                }
                return true;
            }

            public bool IsActive
            {
                get { return mActive; }
            }

            public void Undo()
            {
                if (!mActive) return;

                switch (edType)
                {
                    case MapEditAction.PaintTile:
                        mmw.map.Map[posX, posY] = t1;
                        //mmw.Invalidate(new Rectangle((posX - mmw.offX) * 32, (posY - mmw.offY) * 32, 32, 32));
                        mmw.Invalidate();
                        mActive = false;
                        break;

                    case MapEditAction.ChangeBackColor:
                        //mmw.BackColor = c1;
                        mmw.mBackColor = c1;
                        mmw.mBackBrush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, 32, 32), mmw.mBackColor, mmw.mBackColor, System.Drawing.Drawing2D.LinearGradientMode.Vertical);
                        mmw.Invalidate();
                        mActive = false;
                        break;
                }
            }

            public void Redo()
            {
                if (mActive) return;

                switch (edType)
                {
                    case MapEditAction.PaintTile:
                        mmw.map.Map[posX, posY] = t2;
                        //mmw.Invalidate(new Rectangle((posX - mmw.offX) * 32, (posY - mmw.offY) * 32, 32, 32));
                        mmw.Invalidate();
                        mActive = true;
                        break;

                    case MapEditAction.ChangeBackColor:
                        //mmw.BackColor = c2;
                        mmw.mBackColor = c2;
                        mmw.mBackBrush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, 32, 32), mmw.mBackColor, mmw.mBackColor, System.Drawing.Drawing2D.LinearGradientMode.Vertical);
                        mmw.Invalidate();
                        mActive = true;
                        break;
                }
            }
        }

        private void OnSelectionChange()
        {
            if(SelectionChange != null) SelectionChange(this, new SelectionChangeEventArgs(selX, selY));
        }

        private void OnMapChange()
        {
            if (MapChange != null) MapChange(this, new EventArgs());
        }

        public MapView()
        {
            map = new TileMap(25, 25);
            mBackColor = Color.Blue;
            mBackBrush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, 32, 32), mBackColor, mBackColor, System.Drawing.Drawing2D.LinearGradientMode.Vertical);
            isChanged = false;
            mActionList = new List<MapEditEntry>();

            InitializeComponent();
            this.Paint += new PaintEventHandler(MapView_Paint);
            this.MouseWheel += new MouseEventHandler(MapView_MouseWheel);
            this.MouseDown += new MouseEventHandler(MapView_MouseDown);
            this.MouseMove += new MouseEventHandler(MapView_MouseMove);
            this.MouseUp += new MouseEventHandler(MapView_MouseUp);
        }

        void MapView_MouseUp(object sender, MouseEventArgs e)
        {
            if (state != null)
                state.MapView_MouseUp(sender, e);
        }

        void MapView_MouseMove(object sender, MouseEventArgs e)
        {
            int osX = selX, osY = selY;

            selX = e.X / 32 - offX;
            selY = e.Y / 32 - offY;

            if (osX != selX || osY != selY)
            {
                this.Invalidate(new Rectangle((osX + offX) * 32, (osY + offY) * 32, 32, 32));
                this.Invalidate(new Rectangle((selX + offX) * 32, (selY + offY) * 32, 32, 32));
                OnSelectionChange();
            }

            if (map != null)
            {
                selIn = (selX >= 0 && selY >= 0 && selX < map.Width && selY < map.Height);
            }

            if (e.Button == primBtn)
            {
                if (state != null)
                    state.MapView_MouseMove(sender, e);
                else
                    PaintTile();
            }

            if (e.Button == scrollBtn && scrollingEnabled)
            {
                //EraseTile();
                int ndelX = (e.X - orgX) / 32;
                int ndelY = (e.Y - orgY) / 32;

                if (ndelX != delX || ndelY != delY)
                {
                    delX = ndelX;
                    delY = ndelY;

                    offX = soffX + delX;
                    offY = soffY + delY;
                    this.Invalidate();
                }
            }
        }

        public void PaintTile()
        {
            if (Chooser != null)
            {
                if (selIn)
                {
                    NewAction(new MapEditEntry(this, selX, selY, Chooser.SelectedTile));
                    map.Map[selX, selY] = Chooser.SelectedTile;
                    
                    //isChanged = true;
                    this.Invalidate(new Rectangle((selX + offX) * 32, (selY + offY) * 32 - 32, 32, 64));
                }   
            }
        }

        public void PaintTile(int x, int y, Tile t)
        {
            if (x < 0 || x >= map.Width || y < 0 || y >= map.Height) return;
            if (map.Map[x, y] == t) return;
            NewAction(new MapEditEntry(this, x, y, t));
            map.Map[x, y] = t;
            this.Invalidate(new Rectangle((x + offX) * 32, (y + offY) * 32 - 32, 32, 64));
        }

        private void NewAction(MapEditEntry me)
        {
            if (me.Redundant) return;

            if (mActionID == mActionList.Count - 1)
            {
                mActionList.Add(me);
                mActionID++;
            }
            else
            {
                mActionList.RemoveRange(mActionID + 1, mActionList.Count - mActionID - 1);
                mActionList.Add(me);
                mActionID++;
            }
            isChanged = true;
        }

        private void EraseTile()
        {
            if (selIn)
            {
                NewAction(new MapEditEntry(this, selX, selY, null));
                map.Map[selX, selY] = null;
                this.Invalidate(new Rectangle((selX - offX) * 32, (selY - offY) * 32, 32, 32));
            }
        }

        void MapView_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == primBtn)
            {
                if (state != null)
                    state.MapView_MouseDown(sender, e);
                else
                    PaintTile();
            }

            if (e.Button == scrollBtn)
            {
                //EraseTile();
                orgX = e.X;
                orgY = e.Y;
                delX = delY = 0;
                soffX = offX;
                soffY = offY;
            }

            if (e.Button == altBtn)
            {
                if (state != null)
                    state.MapView_MouseDown(sender, e);
                else
                    if (tc != null) tc.NulToggle();
            }
        }

        void MapView_MouseWheel(object sender, MouseEventArgs e)
        {
            if (e.Delta > 0)
            {
                if (tc != null)
                {
                    tc.PreviousTile();
                }
            }
            else
            {
                if (tc != null)
                {
                    tc.NextTile();
                }
            }

            if (state != null)
                state.MapView_MouseWheel(sender, e);
        }

        public void DoUndo()
        {
            if (!CanUndo) return;

            mActionList[mActionID].Undo();
            mActionID--;
        }

        public void DoRedo()
        {
            if (!CanRedo) return;

            mActionList[mActionID + 1].Redo();
            mActionID++;
        }

        void MapView_Paint(object sender, PaintEventArgs e)
        {
            int maxX = this.Width / TILESIZE + 1;
            int maxY = this.Height / TILESIZE + 1;

            Brush b = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, 32, 32),
                Color.FromArgb(48, 255, 255, 255), Color.FromArgb(64, 255, 255, 0), System.Drawing.Drawing2D.LinearGradientMode.Vertical);
            Brush sb = new SolidBrush(Color.FromArgb(64, 0, 255, 255));

            for(int x = 0; x < maxX; ++x)
            {
                for(int y = 0; y < maxY; ++y)
                {
                    if (x - offX >= 0 && x - offX < map.Width && y - offY >= 0 && y - offY < map.Height)
                    {
                        e.Graphics.FillRectangle(mBackBrush, new Rectangle(x * TILESIZE, y * TILESIZE, TILESIZE, TILESIZE));
                        
                    }
                    else
                    {
                        e.Graphics.FillRectangle(Brushes.Black, new Rectangle(x * TILESIZE, y * TILESIZE, TILESIZE, TILESIZE));
                    }

                    if (x - offX >= 0 && x - offX < map.Width && y - offY >= 0 && y - offY < map.Height && map.Map[x - offX, y - offY] != null && map.Map[x - offX, y - offY].TileImage != null)
                    {
                        e.Graphics.DrawImageUnscaled(map.Map[x - offX, y - offY].TileImage,
                            x * TILESIZE - map.Map[x - offX, y - offY].OffsetX,
                            y * TILESIZE - map.Map[x - offX, y - offY].OffsetY);
                    }

                    if (x - offX == selX && y - offY == selY)
                    {
                        e.Graphics.FillRectangle(b, new Rectangle(x * TILESIZE, y * TILESIZE, TILESIZE, TILESIZE));
                    }

                    if (rectSelEnabled && (x - offX) >= selStart.X && (x - offX) <= selEnd.X &&
                        (y - offY) >= selStart.Y && (y - offY) <= selEnd.Y)
                    {
                        e.Graphics.FillRectangle(sb, new Rectangle(x * TILESIZE, y * TILESIZE, TILESIZE, TILESIZE));
                    }
                }
            }
        }

        public TileMap Map
        {
            get { return map; }
            set 
            { 
                map = value;
                mActionList.Clear();
                mActionID = -1;
                BackColor = Color.Blue;
                this.Invalidate();
                OnMapChange();
            }
        }

        public TileChooser Chooser
        {
            get { return tc; }
            set { tc = value; }
        }

        public override Color BackColor
        {
            get { return mBackColor; }
            set
            {
                NewAction(new MapEditEntry(this, value));
                mBackColor = value;
                mBackBrush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, 32, 32), mBackColor, mBackColor, System.Drawing.Drawing2D.LinearGradientMode.Vertical);
                map.BackColor = value;
                this.Invalidate();
            }
        }

        public bool IsChanged
        {
            get { return isChanged; }
        }

        public bool CanUndo
        {
            get { return mActionID >= 0; }
        }

        public bool CanRedo
        {
            get { return mActionList.Count > mActionID + 1; }
        }

        public void ClearUndo()
        {
            mActionList.Clear();
            mActionID = -1;
        }

        public bool LoadMap(string FileName)
        {
            if (!map.LoadFromFile(FileName)) return false;
            
            if(this.tc != null) tc.Invalidate();
            this.ClearUndo();

            mBackColor = map.BackColor;
            mBackBrush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, 32, 32), mBackColor, mBackColor, System.Drawing.Drawing2D.LinearGradientMode.Vertical);
            this.Invalidate();
            OnMapChange();
            return true;
        }

        public void ResizeMap(int W, int H, ContentAlignment A)
        {
            map.Resize(W, H, A);
            OnMapChange();
            this.Invalidate();
        }
    }

    public class SelectionChangeEventArgs : EventArgs
    {
        public int posX, posY;

        public SelectionChangeEventArgs(int PosX, int PosY)
            : base()
        {
            posX = PosX;
            posY = PosY;
        }
    }
}
